﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Configuration;
using Gollie.Service;
using Gollie.Administration.Service.Tasks;
using NLog;

namespace Gollie.Administration.Service
{
	internal class MainTask
	{
		private static readonly Logger logger = LogManager.GetCurrentClassLogger();

		internal Task mainTask;
		private static ConcurrentDictionary<int, Task> subTasks = new ConcurrentDictionary<int, Task>();
		CancellationTokenSource cts;

		private Collection<AdministrationTask> administrationTasks = new Collection<AdministrationTask>();

		private TaskConfiguration taskConfiguration;

		internal MainTask(CancellationTokenSource cts)
		{
			logger.Info("Starting GollieAdministrationService");

			this.cts = cts;

			this.ReadTasks();

			mainTask = Task.Factory.StartNew(() => Run(cts.Token), cts.Token);

			logger.Info("Reading taskconfiguration...");
			taskConfiguration = new TaskConfiguration();
			logger.Info("Finished reading taskconfiguration...");
		}

		private void ReadTasks()
		{
			logger.Info("Registering administration tasks...");

			AdministrationTasksConfigSectionHandler tasks = (AdministrationTasksConfigSectionHandler)ConfigurationManager.GetSection("Administration");

			foreach (TaskConfigElement taskConfig in tasks.Tasks)
			{
				logger.Info("Registering task {0}...", taskConfig.Name);

				AdministrationTask administrationTask = new AdministrationTask(taskConfig, cts);

				administrationTasks.Add(administrationTask);

				logger.Info("Finished registering task {0}.", taskConfig.Name);
			}

			logger.Info("Finished registering administration tasks.");
		}

		internal void Run(CancellationToken ct)
		{
			logger.Info("GollieAdministrationService is now running");
			try
			{
				while (true)
				{
					ct.ThrowIfCancellationRequested();

					try
					{
						DateTime now = DateTime.Now;

						foreach (AdministrationTask task in this.administrationTasks)
						{
							if (task.Process(now))
							{
								TaskProperties taskProperties = null;
								taskConfiguration.TryGetValue(task.Name, out taskProperties);

								IAdministrationTask newTask = (IAdministrationTask)Activator.CreateInstance(task.Type);

								Task t = new Task(() => newTask.Run(cts.Token, taskProperties), cts.Token);
								
								logger.Info("Start administration task {0}:...(previous: {1:yyyy-MM-dd, HH:mm:ss})", task.Name, task.LastProcessTime);
								
								subTasks.TryAdd(t.Id, t);
								Task cont = t.ContinueWith((antecedent) =>
								{
									Task t2; subTasks.TryRemove(antecedent.Id, out t2); if (t2 != null) t2.Dispose();
									logger.Info("Finished administration task {0}", task.Name); 
								});

								task.LastProcessTime = now;

								t.Start();
							}
						}
					}
					catch (Exception e)
					{
						logger.FatalException("", e);
					}

					//Sleep 25 seconds, so we're sure we'll be checking at least twice a minute if a task should run
					Task.Delay(25000, ct).Wait(ct);
				}
			}
			catch (OperationCanceledException)
			{
				logger.Info("MainTask exited with CancellationRequested, waiting for all tasks ({0}) to finish", subTasks.Count);

				Task[] tasks = subTasks.Values.ToArray<Task>();
				if (Task.WaitAll(tasks, 10000))
				{
					logger.Info("All tasks have finished");
				}
				else
				{
					logger.Info("Not all tasks could finish in time. Still {0} running. Stopping anyway...", subTasks.Count);
				}
			}
		}

		internal void Cancel()
		{
			logger.Info("Cancelling service tasks...");
 
			if (mainTask == null)
			{
				logger.Info("Main service task is not running.");
				return;
			}

			if (mainTask.IsFaulted)
			{
				logger.Info("Main service task is in faulted state");

				DisposeMainTask();

				return;
			}

			if (mainTask.IsCompleted)
			{
				logger.Info("Main service task is in completed state ");

				DisposeMainTask();

				return;
			}

			logger.Info("Cancelling main task...");
			this.cts.Cancel();
			if (this.WaitMainTask())
				logger.Info("GollieAdministrationService stopped gracefully");
			else
				logger.Warn("Unable to cancel main task within time...GollieAdministrationService stopped non-gracefully");
		}

		private bool WaitMainTask()
		{
			for (var i = 0; i < 6; i++)
			{
				// wait for up to 10 seconds

				logger.Info("Waiting for main task to finish...");
				if (mainTask.Wait(10000))
				{
					// it has stopped
					logger.Info("Main task has stopped gracefully");
					DisposeMainTask();
					return true;
				}

				// ask for an additional 15 seconds

				Thread.Sleep(15000);
			}

			return false;
		}

		private void DisposeMainTask()
		{
			mainTask.Wait();

			mainTask.Dispose();

			mainTask = null;
		}
	}
}
